//
//  HKRouter.m
//  HKUtilities
//
//  Created by 胡锦涛 on 2018/11/11.
//  Copyright © 2018 胡锦涛. All rights reserved.
//

#import "HKRouter.h"
#import <objc/runtime.h>
#import "HKPrefixHeader.h"
#define PushTargetController(targetVC) [[HKRouter shared].currentNavigationController pushViewController:targetVC animated:YES];
@class HKRouter;
#pragma mark - UIViewController+HKRouter
@implementation UIViewController (HKRouter)

static char kAssociatedParamsObjectKey;

- (HKHandlerCallBack)handlerBlock {
    return  objc_getAssociatedObject(self, &kAssociatedParamsObjectKey);
}

- (void)setHandlerBlock:(HKHandlerCallBack)handlerBlock {
    objc_setAssociatedObject(self, &kAssociatedParamsObjectKey, handlerBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)hk_isPresent {
    return  objc_getAssociatedObject(self, &kAssociatedParamsObjectKey);
}

-(void)setHk_isPresent:(BOOL)hk_isPresent{
    objc_setAssociatedObject(self, &kAssociatedParamsObjectKey, @(hk_isPresent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

#pragma mark - UINavigationController+HKRouter

@interface UINavigationController (HKRouter)

@end
@implementation UINavigationController (HKRouter)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        swizzleMethod(class, @selector(viewWillAppear:), @selector(aop_NavigationViewWillAppear:));
    });
}

- (void)aop_NavigationViewWillAppear:(BOOL)animation {
    [self aop_NavigationViewWillAppear:animation];
    [HKRouter shared].currentNavigationController = self;
}

void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end
#pragma mark - HKRouter
NSString * const kHKRouterParamsKeySwiftTargetModuleName = @"kHKRouterParamsKeySwiftTargetModuleName";
@interface HKRouter ()
@property (nonatomic, strong) NSMutableDictionary *cachedTarget;
@property (nonatomic, strong) NSMutableDictionary *routes;
@end
static HKRouter * mediator = nil;
@implementation HKRouter
+(instancetype)shared {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mediator = [[self alloc] init];
    });
    return mediator;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mediator = [super allocWithZone:zone];
    });
    return mediator;
}

+(void)hk_registerRoutePattern:(NSString *)routePattern targetName:(NSString *)targetName {
    [self hk_registerRoutePattern:routePattern targetName:targetName handler:nil];
}

+ (void)hk_registerRoutePattern:(NSString *)routePattern targetName:(NSString *)targetName handler:(void(^)(NSString *callTag, id parameters))handlerBlock {
    if (!routePattern.length && !targetName.length) return;
    
    [[self shared] addRoutePattern:routePattern targetControllerName:targetName handler:handlerBlock];
}

+(void)hk_removeRoutePattern:(NSString *)routePattern {
    [[self shared] removeRoutePattern:routePattern];
}

+(void)hk_removeRoutePatternWithController:(Class)className {
    [[self shared] removeRoutePatternWithController:className];
}

+ (BOOL)hk_startRoute:(NSString *)routePattern {
    
    if (!routePattern.length) return NO;
    
    NSURL *URL = [NSURL URLWithString:[routePattern stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
    
    return [self hk_startRouteWithURL:URL];
}

+ (BOOL)hk_startRouteWithURL:(NSURL *)URL {
    if (!URL) return NO;
    
    return [self analysisRoutePattern:URL];
}

+ (BOOL)analysisRoutePattern:(NSURL *)URL{
    
    NSString *routePattern = [URL absoluteString];
    
    NSURLComponents *components = [NSURLComponents componentsWithString:routePattern];
    
    NSString *scheme = components.scheme;
    
    //scheme规则自己添加
    if (![scheme isEqualToString:@"hk"]) {
        NSLog(@"scheme规则不匹配");
        return NO;
    }
    
    if (components.host.length > 0 && (![components.host isEqualToString:@"localhost"] && [components.host rangeOfString:@"."].location == NSNotFound)) {
        NSString *host = [components.percentEncodedHost copy];
        components.host = @"/";
        components.percentEncodedPath = [host stringByAppendingPathComponent:(components.percentEncodedPath ?: @"")];
    }
    
    NSString *path = [components percentEncodedPath];
    
    if (components.fragment != nil) {
        BOOL fragmentContainsQueryParams = NO;
        NSURLComponents *fragmentComponents = [NSURLComponents componentsWithString:components.percentEncodedFragment];
        
        if (fragmentComponents.query == nil && fragmentComponents.path != nil) {
            fragmentComponents.query = fragmentComponents.path;
        }
        
        if (fragmentComponents.queryItems.count > 0) {
            fragmentContainsQueryParams = fragmentComponents.queryItems.firstObject.value.length > 0;
        }
        
        if (fragmentContainsQueryParams) {
            components.queryItems = [(components.queryItems ?: @[]) arrayByAddingObjectsFromArray:fragmentComponents.queryItems];
        }
        
        if (fragmentComponents.path != nil && (!fragmentContainsQueryParams || ![fragmentComponents.path isEqualToString:fragmentComponents.query])) {
            path = [path stringByAppendingString:[NSString stringWithFormat:@"#%@", fragmentComponents.percentEncodedPath]];
        }
    }
    
    if (path.length > 0 && [path characterAtIndex:0] == '/') {
        path = [path substringFromIndex:1];
    }
    
    if (path.length > 0 && [path characterAtIndex:path.length - 1] == '/') {
        path = [path substringToIndex:path.length - 1];
    }
    
    //获取queryItem
    NSArray <NSURLQueryItem *> *queryItems = [components queryItems] ?: @[];
    NSMutableDictionary *queryParams = [NSMutableDictionary dictionary];
    for (NSURLQueryItem *item in queryItems) {
        if (item.value == nil) {
            continue;
        }
        
        if (queryParams[item.name] == nil) {
            queryParams[item.name] = item.value;
        } else if ([queryParams[item.name] isKindOfClass:[NSArray class]]) {
            NSArray *values = (NSArray *)(queryParams[item.name]);
            queryParams[item.name] = [values arrayByAddingObject:item.value];
        } else {
            id existingValue = queryParams[item.name];
            queryParams[item.name] = @[existingValue, item.value];
        }
    }
    
    NSDictionary *params = queryParams.copy;
    
    return [[self shared] pushTargetControllerWithRoutePattern:&routePattern queryParams:&params];
}

#pragma mark - instance method
- (void)addRoutePattern:(NSString *)routePattern targetControllerName:(NSString *)targetControllerName handler:(void (^)(NSString *, id))handlerBlock {
    if (!routePattern.length && !targetControllerName.length) return;
    
    NSArray *pathComponents = [self pathComponentsFromRoutePattern:routePattern];
    
    if (pathComponents.count > 1) {
        //for example:hk.Amodule.product.detail
        NSString *components = [pathComponents componentsJoinedByString:@"."];
        
        NSMutableDictionary *routes = self.routes;
        
        if (![routes objectForKey:routePattern]) {
            NSMutableDictionary *controllerHandler = [NSMutableDictionary dictionary];
            if (handlerBlock) {
                [controllerHandler setValue:[handlerBlock copy] forKey:targetControllerName];
                routes[components] = controllerHandler;
            }else{
                routes[components] = targetControllerName;
            }
        }
    }
    
}

- (void)removeRoutePattern:(NSString *)routePattern {
    NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:[self pathComponentsFromRoutePattern:routePattern]];
    
    if (pathComponents.count >= 1) {
        NSString *components = [pathComponents componentsJoinedByString:@"."];
        
        NSMutableDictionary *routes = self.routes;
        
        if ([routes objectForKey:components]) {
            [routes removeObjectForKey:components];
        }
    }
}

- (void)removeRoutePatternWithController:(Class)class {
    NSString *classString = NSStringFromClass(class);
    
    [self.routes enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        NSString *targetControllerName = nil;
        
        if ([obj isKindOfClass:[NSString class]]) {
            targetControllerName = (NSString *)obj;
        }else if ([obj isKindOfClass:[NSDictionary class]]) {
            NSDictionary *controllerHandler = (NSDictionary *)obj;
            targetControllerName = controllerHandler.allKeys.firstObject;
        }
        
        if ([targetControllerName isEqualToString:classString]) {
            [self.routes removeObjectForKey:key];
            
            *stop = YES;
        }
    }];
}

- (NSArray *)pathComponentsFromRoutePattern:(NSString*)routePattern {
    NSMutableArray *pathComponents = [NSMutableArray array];
    
    if ([routePattern rangeOfString:@"://"].location != NSNotFound) {
        
        NSArray *pathSegments = [routePattern componentsSeparatedByString:@"://"];
        
        [pathComponents addObject:pathSegments[0]];
        
        routePattern = pathSegments.lastObject;
        if (!routePattern.length) {
            [pathComponents addObject:@"~"];
        }
    }
    
    for (NSString *pathComponent in [[NSURL URLWithString:routePattern] pathComponents]) {
        if ([pathComponent isEqualToString:@"/"]) continue;
        if ([[pathComponent substringToIndex:1] isEqualToString:@"?"]) break;
        [pathComponents addObject:pathComponent];
    }
    
    return [pathComponents copy];
}

- (BOOL)pushTargetControllerWithRoutePattern:(NSString **)routePattern queryParams:(NSDictionary **)queryParams {
    
    BOOL canOpen = NO;
    
    NSString *targetRoutePattern = *routePattern;
    
    NSDictionary *targetQueryParams = *queryParams;
    
    NSArray *pathComponents = [self pathComponentsFromRoutePattern:targetRoutePattern];
    
    NSString *components = [pathComponents componentsJoinedByString:@"."];
    
    id routesValue = self.routes[components];//要跳转的ViewController
    
    NSString *targetControllerName = nil;
    NSDictionary *controllerHandler = nil;
    
    if ([routesValue isKindOfClass:[NSString class]]) {
        targetControllerName = (NSString *)routesValue;
    }else if ([routesValue isKindOfClass:[NSDictionary class]]) {
        controllerHandler = (NSDictionary *)routesValue;
        targetControllerName = controllerHandler.allKeys.firstObject;
    }
    
    Class targetClass = NSClassFromString(targetControllerName);
    
    SEL selector = NSSelectorFromString(@"hk_createViewController:");
    
    if ([targetClass respondsToSelector:selector]) {
        
        UIViewController *targetController = [targetClass hk_createViewController:targetQueryParams];
        
        if (targetController) {
            if (controllerHandler) {
                HKHandlerCallBack handlerBlock = [controllerHandler valueForKey:targetControllerName];
                
                if (handlerBlock) {
                    targetController.handlerBlock = handlerBlock;
                }
            }
            if ([components hk_containsString:@"hkPresent"]) {
                [[HKRouter shared].currentNavigationController hk_present:targetController];
            }else {
                //push
                PushTargetController(targetController);
            }
            
            
            canOpen = YES;
        }else{
            NSLog(@"未找到相关类!");
        }
    }else{
        
        NSString *errorInfo = [NSString stringWithFormat:@"请让目标控制器遵守HKRouter的HKRouterProtocol协议"];
        NSLog(@"%@",errorInfo);
    }
    
    return canOpen;
}

/*
 scheme://[target]/[action]?[params]
 url sample:
 aaa://targetA/actionB?id=1234
 */
+ (id)hk_remotePerformWithUrl:(NSString *)urlStr {
    return [[HKRouter shared] hk_remotePerformWithUrl:urlStr];
}

+ (id)hk_remotePerformWithUrl:(NSString *)urlStr handler:(HKRouterHandler)handler {
    return [[HKRouter shared] hk_remotePerformWithUrl:urlStr handler:handler];
}

+ (id)hk_localPerformTarget:(NSString *)targetName action:(NSString *)actionName param:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget {
    return [[HKRouter shared] hk_localPerformTarget:targetName action:actionName param:params shouldCacheTarget:shouldCacheTarget];
}

+ (void)hk_releaseCachedTargetWithTargetName:(NSString *)targetName {
    [[HKRouter shared] hk_releaseCachedTargetWithTargetName:targetName];
}
- (id)hk_remotePerformWithUrl:(NSString *)urlStr {
    return [self hk_remotePerformWithUrl:urlStr handler:nil];
}

- (id)hk_remotePerformWithUrl:(NSString *)urlStr handler:(HKRouterHandler)handler{
    NSURL *url = [NSURL URLWithString:urlStr];
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    NSString *urlString = [url query];
    //切割字符串
    for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
        NSArray *elts = [param componentsSeparatedByString:@"="];
        if([elts count] < 2) continue;
        id firstEle = [elts firstObject];
        id lastEle = [elts lastObject];
        if (firstEle && lastEle) {
            [params setObject:lastEle forKey:firstEle];
        }
    }
    
    // 这里这么写主要是出于安全考虑，防止黑客通过远程方式调用本地模块。
    NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
    if ([actionName hasPrefix:@"native"]) {
        return @(NO);
    }
    
    id result = [self hk_localPerformTarget:url.host action:actionName param:params shouldCacheTarget:NO];
    if (handler) {
        if (result) {
            handler(@{@"result":result});
        } else {
            handler(@{});
        }
    }
    return result;
    
}

- (id)hk_localPerformTarget:(NSString *)targetName action:(NSString *)actionName param:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget{
    //这个目标的类名字符串
    NSString *swiftModuleName = params[kHKRouterParamsKeySwiftTargetModuleName];
    
    // generate target
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    
    // generate action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 这里是处理无响应请求的地方之一，这个hk做得比较简单，如果没有可以响应的target，就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上，然后处理这种请求的
        [self noTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }
    
    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 这里是处理无响应请求的地方，如果无响应，则尝试调用对应target的notFound方法统一处理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 这里也是处理无响应请求的地方，在notFound都没有的时候，这个hk是直接return了。实际开发过程中，可以用前面提到的固定的target顶上的。
            [self noTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            [self.cachedTarget removeObjectForKey:targetClassString];
            return nil;
        }
    }
}

- (void)hk_releaseCachedTargetWithTargetName:(NSString *)targetName
{
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    [self.cachedTarget removeObjectForKey:targetClassString];
}


#pragma mark - private methods
- (void)noTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
    SEL action = NSSelectorFromString(@"Action_response:");
    NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
    
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    params[@"originParams"] = originParams;
    params[@"targetString"] = targetString;
    params[@"selectorString"] = selectorString;
    
    [self safePerformAction:action target:target params:params];
}

//通过对象调用指定的方法
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
    NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
    if(methodSig == nil) {
        return nil;
    }
    const char* retType = [methodSig methodReturnType];
    
    if (strcmp(retType, @encode(void)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        return nil;
    }
    
    if (strcmp(retType, @encode(NSInteger)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
    if (strcmp(retType, @encode(BOOL)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        BOOL result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
    if (strcmp(retType, @encode(CGFloat)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        CGFloat result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
    if (strcmp(retType, @encode(NSUInteger)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSUInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}

#pragma mark - getters and setters
- (NSMutableDictionary *)cachedTarget
{
    if (_cachedTarget == nil) {
        _cachedTarget = [[NSMutableDictionary alloc] init];
    }
    return _cachedTarget;
}

#pragma mark - 其他
- (NSMutableDictionary *)routes {
    if (!_routes) {
        _routes = [[NSMutableDictionary alloc] init];
    }
    return _routes;
}
@end
